﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Reflection;
using NLite.Collections;

namespace NLite.Internal
{
    static class MetadataViewProvider
    {
        internal static readonly Type DefaultMappingViewType = typeof(PropertySet);

        public static object GetMappingView(this IPropertySet mappings, Type metadataViewType)
        {
            if (typeof(IEnumerable<KeyValuePair<string, object>>).IsAssignableFrom(metadataViewType))
                return mappings;
            
            Type proxyType;
            if (metadataViewType.IsInterface)
            {
                try
                {
                    proxyType = MetadataViewGenerator.GenerateView(metadataViewType);
                }
                catch (TypeLoadException ex)
                {
                    throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "NotSupportedInterfaceMappingView", metadataViewType.FullName), ex);
                }
            }
            else
            {
                proxyType = metadataViewType;
            }

            try
            {
                return Activator.CreateInstance(proxyType, mappings);
            }
            catch (MissingMethodException ex)
            {
                // Unable to create an Instance of the Metadata view '{0}' because a constructor could not be selected.  Ensure that the type implements a constructor which takes an argument of type IDictionary<string, object>.
                throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
                    "CompositionException_MappingViewInvalidConstructor",
                    proxyType.AssemblyQualifiedName), ex);
            }
            catch (TargetInvocationException ex)
            {
                //Unwrap known failures that we want to present as CompositionContractMismatchException
                if (metadataViewType.IsInterface)
                {
                    if (ex.InnerException.GetType() == typeof(InvalidCastException))
                    {
                        // Unable to create an Instance of the Metadata view {0} because the exporter exported the metadata for the item {1} with the value {2} as type {3} but the view imports it as type {4}.
                        throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
                            "InvalidCastOnMappingField",
                            ex.InnerException.Data[MetadataViewGenerator.MappingViewType],
                            ex.InnerException.Data[MetadataViewGenerator.MappingItemKey],
                            ex.InnerException.Data[MetadataViewGenerator.MappingItemValue],
                            ex.InnerException.Data[MetadataViewGenerator.MappingItemSourceType],
                            ex.InnerException.Data[MetadataViewGenerator.MappingItemTargetType]), ex);
                    }
                    else if (ex.InnerException.GetType() == typeof(NullReferenceException))
                    {
                        // Unable to create an Instance of the Metadata view {0} because the exporter exported the metadata for the item {1} with a null value and null is not a valid value for type {2}.
                        throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture,
                            "NullReferenceOnMetadataField",
                            ex.InnerException.Data[MetadataViewGenerator.MappingViewType],
                            ex.InnerException.Data[MetadataViewGenerator.MappingItemKey],
                            ex.InnerException.Data[MetadataViewGenerator.MappingItemTargetType]), ex);
                    }
                }
                throw;
            }
        }

        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        public static TMappingView GetMappingView<TMappingView>(this IPropertySet mappings) 
        {
            Type metadataViewType = typeof(TMappingView);
            return (TMappingView)GetMappingView(mappings, typeof(TMappingView));
           
        }
    }
}
